1 /*
2 * Copyright (c) 2000, 2008, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package javax.swing.text.html;
27
28 import java.awt.*;
29 import java.awt.event.*;
30 import java.beans.*;
31 import java.util.*;
32 import javax.swing.*;
33 import javax.swing.event.*;
34 import javax.swing.text.*;
35 import javax.accessibility.*;
36 import java.text.BreakIterator;
37
38 /*
39 * The AccessibleHTML class provide information about the contents
40 * of a HTML document to assistive technologies.
41 *
42 * @author Lynn Monsanto
43 */
44 class AccessibleHTML implements Accessible {
45
46 /**
47 * The editor.
48 */
49 private JEditorPane editor;
50 /**
51 * Current model.
52 */
53 private Document model;
54 /**
55 * DocumentListener installed on the current model.
56 */
57 private DocumentListener docListener;
58 /**
59 * PropertyChangeListener installed on the editor
60 */
61 private PropertyChangeListener propChangeListener;
62 /**
63 * The root ElementInfo for the document
64 */
65 private ElementInfo rootElementInfo;
66 /*
67 * The root accessible context for the document
68 */
69 private RootHTMLAccessibleContext rootHTMLAccessibleContext;
70
71 public AccessibleHTML(JEditorPane pane) {
72 editor = pane;
73 propChangeListener = new PropertyChangeHandler();
74 setDocument(editor.getDocument());
75
76 docListener = new DocumentHandler();
77 }
78
79 /**
80 * Sets the document.
81 */
82 private void setDocument(Document document) {
83 if (model != null) {
84 model.removeDocumentListener(docListener);
85 }
86 if (editor != null) {
87 editor.removePropertyChangeListener(propChangeListener);
88 }
89 this.model = document;
90 if (model != null) {
91 if (rootElementInfo != null) {
92 rootElementInfo.invalidate(false);
93 }
94 buildInfo();
95 model.addDocumentListener(docListener);
96 }
97 else {
98 rootElementInfo = null;
99 }
100 if (editor != null) {
101 editor.addPropertyChangeListener(propChangeListener);
102 }
103 }
104
105 /**
106 * Returns the Document currently presenting information for.
107 */
108 private Document getDocument() {
109 return model;
110 }
111
112 /**
113 * Returns the JEditorPane providing information for.
114 */
115 private JEditorPane getTextComponent() {
116 return editor;
117 }
118
119 /**
120 * Returns the ElementInfo representing the root Element.
121 */
122 private ElementInfo getRootInfo() {
123 return rootElementInfo;
124 }
125
126 /**
127 * Returns the root <code>View</code> associated with the current text
128 * component.
129 */
130 private View getRootView() {
131 return getTextComponent().getUI().getRootView(getTextComponent());
132 }
133
134 /**
135 * Returns the bounds the root View will be rendered in.
136 */
137 private Rectangle getRootEditorRect() {
138 Rectangle alloc = getTextComponent().getBounds();
139 if ((alloc.width > 0) && (alloc.height > 0)) {
140 alloc.x = alloc.y = 0;
141 Insets insets = editor.getInsets();
142 alloc.x += insets.left;
143 alloc.y += insets.top;
144 alloc.width -= insets.left + insets.right;
145 alloc.height -= insets.top + insets.bottom;
146 return alloc;
147 }
148 return null;
149 }
150
151 /**
152 * If possible acquires a lock on the Document. If a lock has been
153 * obtained a key will be retured that should be passed to
154 * <code>unlock</code>.
155 */
156 private Object lock() {
157 Document document = getDocument();
158
159 if (document instanceof AbstractDocument) {
160 ((AbstractDocument)document).readLock();
161 return document;
162 }
163 return null;
164 }
165
166 /**
167 * Releases a lock previously obtained via <code>lock</code>.
168 */
169 private void unlock(Object key) {
170 if (key != null) {
171 ((AbstractDocument)key).readUnlock();
172 }
173 }
174
175 /**
176 * Rebuilds the information from the current info.
177 */
178 private void buildInfo() {
179 Object lock = lock();
180
181 try {
182 Document doc = getDocument();
183 Element root = doc.getDefaultRootElement();
184
185 rootElementInfo = new ElementInfo(root);
186 rootElementInfo.validate();
187 } finally {
188 unlock(lock);
189 }
190 }
191
192 /*
193 * Create an ElementInfo subclass based on the passed in Element.
194 */
195 ElementInfo createElementInfo(Element e, ElementInfo parent) {
196 AttributeSet attrs = e.getAttributes();
197
198 if (attrs != null) {
199 Object name = attrs.getAttribute(StyleConstants.NameAttribute);
200
201 if (name == HTML.Tag.IMG) {
202 return new IconElementInfo(e, parent);
203 }
204 else if (name == HTML.Tag.CONTENT || name == HTML.Tag.CAPTION) {
205 return new TextElementInfo(e, parent);
206 }
207 else if (name == HTML.Tag.TABLE) {
208 return new TableElementInfo(e, parent);
209 }
210 }
211 return null;
212 }
213
214 /**
215 * Returns the root AccessibleContext for the document
216 */
217 public AccessibleContext getAccessibleContext() {
218 if (rootHTMLAccessibleContext == null) {
219 rootHTMLAccessibleContext =
220 new RootHTMLAccessibleContext(rootElementInfo);
221 }
222 return rootHTMLAccessibleContext;
223 }
224
225 /*
226 * The roow AccessibleContext for the document
227 */
228 private class RootHTMLAccessibleContext extends HTMLAccessibleContext {
229
230 public RootHTMLAccessibleContext(ElementInfo elementInfo) {
231 super(elementInfo);
232 }
233
234 /**
235 * Gets the accessibleName property of this object. The accessibleName
236 * property of an object is a localized String that designates the purpose
237 * of the object. For example, the accessibleName property of a label
238 * or button might be the text of the label or button itself. In the
239 * case of an object that doesn't display its name, the accessibleName
240 * should still be set. For example, in the case of a text field used
241 * to enter the name of a city, the accessibleName for the en_US locale
242 * could be 'city.'
243 *
244 * @return the localized name of the object; null if this
245 * object does not have a name
246 *
247 * @see #setAccessibleName
248 */
249 public String getAccessibleName() {
250 if (model != null) {
251 return (String)model.getProperty(Document.TitleProperty);
252 } else {
253 return null;
254 }
255 }
256
257 /**
258 * Gets the accessibleDescription property of this object. If this
259 * property isn't set, returns the content type of this
260 * <code>JEditorPane</code> instead (e.g. "plain/text", "html/text").
261 *
262 * @return the localized description of the object; <code>null</code>
263 * if this object does not have a description
264 *
265 * @see #setAccessibleName
266 */
267 public String getAccessibleDescription() {
268 return editor.getContentType();
269 }
270
271 /**
272 * Gets the role of this object. The role of the object is the generic
273 * purpose or use of the class of this object. For example, the role
274 * of a push button is AccessibleRole.PUSH_BUTTON. The roles in
275 * AccessibleRole are provided so component developers can pick from
276 * a set of predefined roles. This enables assistive technologies to
277 * provide a consistent interface to various tweaked subclasses of
278 * components (e.g., use AccessibleRole.PUSH_BUTTON for all components
279 * that act like a push button) as well as distinguish between sublasses
280 * that behave differently (e.g., AccessibleRole.CHECK_BOX for check boxes
281 * and AccessibleRole.RADIO_BUTTON for radio buttons).
282 * <p>Note that the AccessibleRole class is also extensible, so
283 * custom component developers can define their own AccessibleRole's
284 * if the set of predefined roles is inadequate.
285 *
286 * @return an instance of AccessibleRole describing the role of the object
287 * @see AccessibleRole
288 */
289 public AccessibleRole getAccessibleRole() {
290 return AccessibleRole.TEXT;
291 }
292 }
293
294 /*
295 * Base AccessibleContext class for HTML elements
296 */
297 protected abstract class HTMLAccessibleContext extends AccessibleContext
298 implements Accessible, AccessibleComponent {
299
300 protected ElementInfo elementInfo;
301
302 public HTMLAccessibleContext(ElementInfo elementInfo) {
303 this.elementInfo = elementInfo;
304 }
305
306 // begin AccessibleContext implementation ...
307 public AccessibleContext getAccessibleContext() {
308 return this;
309 }
310
311 /**
312 * Gets the state set of this object.
313 *
314 * @return an instance of AccessibleStateSet describing the states
315 * of the object
316 * @see AccessibleStateSet
317 */
318 public AccessibleStateSet getAccessibleStateSet() {
319 AccessibleStateSet states = new AccessibleStateSet();
320 Component comp = getTextComponent();
321
322 if (comp.isEnabled()) {
323 states.add(AccessibleState.ENABLED);
324 }
325 if (comp instanceof JTextComponent &&
326 ((JTextComponent)comp).isEditable()) {
327
328 states.add(AccessibleState.EDITABLE);
329 states.add(AccessibleState.FOCUSABLE);
330 }
331 if (comp.isVisible()) {
332 states.add(AccessibleState.VISIBLE);
333 }
334 if (comp.isShowing()) {
335 states.add(AccessibleState.SHOWING);
336 }
337 return states;
338 }
339
340 /**
341 * Gets the 0-based index of this object in its accessible parent.
342 *
343 * @return the 0-based index of this object in its parent; -1 if this
344 * object does not have an accessible parent.
345 *
346 * @see #getAccessibleParent
347 * @see #getAccessibleChildrenCount
348 * @see #getAccessibleChild
349 */
350 public int getAccessibleIndexInParent() {
351 return elementInfo.getIndexInParent();
352 }
353
354 /**
355 * Returns the number of accessible children of the object.
356 *
357 * @return the number of accessible children of the object.
358 */
359 public int getAccessibleChildrenCount() {
360 return elementInfo.getChildCount();
361 }
362
363 /**
364 * Returns the specified Accessible child of the object. The Accessible
365 * children of an Accessible object are zero-based, so the first child
366 * of an Accessible child is at index 0, the second child is at index 1,
367 * and so on.
368 *
369 * @param i zero-based index of child
370 * @return the Accessible child of the object
371 * @see #getAccessibleChildrenCount
372 */
373 public Accessible getAccessibleChild(int i) {
374 ElementInfo childInfo = elementInfo.getChild(i);
375 if (childInfo != null && childInfo instanceof Accessible) {
376 return (Accessible)childInfo;
377 } else {
378 return null;
379 }
380 }
381
382 /**
383 * Gets the locale of the component. If the component does not have a
384 * locale, then the locale of its parent is returned.
385 *
386 * @return this component's locale. If this component does not have
387 * a locale, the locale of its parent is returned.
388 *
389 * @exception IllegalComponentStateException
390 * If the Component does not have its own locale and has not yet been
391 * added to a containment hierarchy such that the locale can be
392 * determined from the containing parent.
393 */
394 public Locale getLocale() throws IllegalComponentStateException {
395 return editor.getLocale();
396 }
397 // ... end AccessibleContext implementation
398
399 // begin AccessibleComponent implementation ...
400 public AccessibleComponent getAccessibleComponent() {
401 return this;
402 }
403
404 /**
405 * Gets the background color of this object.
406 *
407 * @return the background color, if supported, of the object;
408 * otherwise, null
409 * @see #setBackground
410 */
411 public Color getBackground() {
412 return getTextComponent().getBackground();
413 }
414
415 /**
416 * Sets the background color of this object.
417 *
418 * @param c the new Color for the background
419 * @see #setBackground
420 */
421 public void setBackground(Color c) {
422 getTextComponent().setBackground(c);
423 }
424
425 /**
426 * Gets the foreground color of this object.
427 *
428 * @return the foreground color, if supported, of the object;
429 * otherwise, null
430 * @see #setForeground
431 */
432 public Color getForeground() {
433 return getTextComponent().getForeground();
434 }
435
436 /**
437 * Sets the foreground color of this object.
438 *
439 * @param c the new Color for the foreground
440 * @see #getForeground
441 */
442 public void setForeground(Color c) {
443 getTextComponent().setForeground(c);
444 }
445
446 /**
447 * Gets the Cursor of this object.
448 *
449 * @return the Cursor, if supported, of the object; otherwise, null
450 * @see #setCursor
451 */
452 public Cursor getCursor() {
453 return getTextComponent().getCursor();
454 }
455
456 /**
457 * Sets the Cursor of this object.
458 *
459 * @param cursor the new Cursor for the object
460 * @see #getCursor
461 */
462 public void setCursor(Cursor cursor) {
463 getTextComponent().setCursor(cursor);
464 }
465
466 /**
467 * Gets the Font of this object.
468 *
469 * @return the Font,if supported, for the object; otherwise, null
470 * @see #setFont
471 */
472 public Font getFont() {
473 return getTextComponent().getFont();
474 }
475
476 /**
477 * Sets the Font of this object.
478 *
479 * @param f the new Font for the object
480 * @see #getFont
481 */
482 public void setFont(Font f) {
483 getTextComponent().setFont(f);
484 }
485
486 /**
487 * Gets the FontMetrics of this object.
488 *
489 * @param f the Font
490 * @return the FontMetrics, if supported, the object; otherwise, null
491 * @see #getFont
492 */
493 public FontMetrics getFontMetrics(Font f) {
494 return getTextComponent().getFontMetrics(f);
495 }
496
497 /**
498 * Determines if the object is enabled. Objects that are enabled
499 * will also have the AccessibleState.ENABLED state set in their
500 * AccessibleStateSets.
501 *
502 * @return true if object is enabled; otherwise, false
503 * @see #setEnabled
504 * @see AccessibleContext#getAccessibleStateSet
505 * @see AccessibleState#ENABLED
506 * @see AccessibleStateSet
507 */
508 public boolean isEnabled() {
509 return getTextComponent().isEnabled();
510 }
511
512 /**
513 * Sets the enabled state of the object.
514 *
515 * @param b if true, enables this object; otherwise, disables it
516 * @see #isEnabled
517 */
518 public void setEnabled(boolean b) {
519 getTextComponent().setEnabled(b);
520 }
521
522 /**
523 * Determines if the object is visible. Note: this means that the
524 * object intends to be visible; however, it may not be
525 * showing on the screen because one of the objects that this object
526 * is contained by is currently not visible. To determine if an object
527 * is showing on the screen, use isShowing().
528 * <p>Objects that are visible will also have the
529 * AccessibleState.VISIBLE state set in their AccessibleStateSets.
530 *
531 * @return true if object is visible; otherwise, false
532 * @see #setVisible
533 * @see AccessibleContext#getAccessibleStateSet
534 * @see AccessibleState#VISIBLE
535 * @see AccessibleStateSet
536 */
537 public boolean isVisible() {
538 return getTextComponent().isVisible();
539 }
540
541 /**
542 * Sets the visible state of the object.
543 *
544 * @param b if true, shows this object; otherwise, hides it
545 * @see #isVisible
546 */
547 public void setVisible(boolean b) {
548 getTextComponent().setVisible(b);
549 }
550
551 /**
552 * Determines if the object is showing. This is determined by checking
553 * the visibility of the object and its ancestors.
554 * Note: this
555 * will return true even if the object is obscured by another (for
556 * example, it is underneath a menu that was pulled down).
557 *
558 * @return true if object is showing; otherwise, false
559 */
560 public boolean isShowing() {
561 return getTextComponent().isShowing();
562 }
563
564 /**
565 * Checks whether the specified point is within this object's bounds,
566 * where the point's x and y coordinates are defined to be relative
567 * to the coordinate system of the object.
568 *
569 * @param p the Point relative to the coordinate system of the object
570 * @return true if object contains Point; otherwise false
571 * @see #getBounds
572 */
573 public boolean contains(Point p) {
574 Rectangle r = getBounds();
575 if (r != null) {
576 return r.contains(p.x, p.y);
577 } else {
578 return false;
579 }
580 }
581
582 /**
583 * Returns the location of the object on the screen.
584 *
585 * @return the location of the object on screen; null if this object
586 * is not on the screen
587 * @see #getBounds
588 * @see #getLocation
589 */
590 public Point getLocationOnScreen() {
591 Point editorLocation = getTextComponent().getLocationOnScreen();
592 Rectangle r = getBounds();
593 if (r != null) {
594 return new Point(editorLocation.x + r.x,
595 editorLocation.y + r.y);
596 } else {
597 return null;
598 }
599 }
600
601 /**
602 * Gets the location of the object relative to the parent in the form
603 * of a point specifying the object's top-left corner in the screen's
604 * coordinate space.
605 *
606 * @return An instance of Point representing the top-left corner of the
607 * object's bounds in the coordinate space of the screen; null if
608 * this object or its parent are not on the screen
609 * @see #getBounds
610 * @see #getLocationOnScreen
611 */
612 public Point getLocation() {
613 Rectangle r = getBounds();
614 if (r != null) {
615 return new Point(r.x, r.y);
616 } else {
617 return null;
618 }
619 }
620
621 /**
622 * Sets the location of the object relative to the parent.
623 * @param p the new position for the top-left corner
624 * @see #getLocation
625 */
626 public void setLocation(Point p) {
627 }
628
629 /**
630 * Gets the bounds of this object in the form of a Rectangle object.
631 * The bounds specify this object's width, height, and location
632 * relative to its parent.
633 *
634 * @return A rectangle indicating this component's bounds; null if
635 * this object is not on the screen.
636 * @see #contains
637 */
638 public Rectangle getBounds() {
639 return elementInfo.getBounds();
640 }
641
642 /**
643 * Sets the bounds of this object in the form of a Rectangle object.
644 * The bounds specify this object's width, height, and location
645 * relative to its parent.
646 *
647 * @param r rectangle indicating this component's bounds
648 * @see #getBounds
649 */
650 public void setBounds(Rectangle r) {
651 }
652
653 /**
654 * Returns the size of this object in the form of a Dimension object.
655 * The height field of the Dimension object contains this object's
656 * height, and the width field of the Dimension object contains this
657 * object's width.
658 *
659 * @return A Dimension object that indicates the size of this component;
660 * null if this object is not on the screen
661 * @see #setSize
662 */
663 public Dimension getSize() {
664 Rectangle r = getBounds();
665 if (r != null) {
666 return new Dimension(r.width, r.height);
667 } else {
668 return null;
669 }
670 }
671
672 /**
673 * Resizes this object so that it has width and height.
674 *
675 * @param d The dimension specifying the new size of the object.
676 * @see #getSize
677 */
678 public void setSize(Dimension d) {
679 Component comp = getTextComponent();
680 comp.setSize(d);
681 }
682
683 /**
684 * Returns the Accessible child, if one exists, contained at the local
685 * coordinate Point.
686 *
687 * @param p The point relative to the coordinate system of this object.
688 * @return the Accessible, if it exists, at the specified location;
689 * otherwise null
690 */
691 public Accessible getAccessibleAt(Point p) {
692 ElementInfo innerMostElement = getElementInfoAt(rootElementInfo, p);
693 if (innerMostElement instanceof Accessible) {
694 return (Accessible)innerMostElement;
695 } else {
696 return null;
697 }
698 }
699
700 private ElementInfo getElementInfoAt(ElementInfo elementInfo, Point p) {
701 if (elementInfo.getBounds() == null) {
702 return null;
703 }
704 if (elementInfo.getChildCount() == 0 &&
705 elementInfo.getBounds().contains(p)) {
706 return elementInfo;
707
708 } else {
709 if (elementInfo instanceof TableElementInfo) {
710 // Handle table caption as a special case since it's the
711 // only table child that is not a table row.
712 ElementInfo captionInfo =
713 ((TableElementInfo)elementInfo).getCaptionInfo();
714 if (captionInfo != null) {
715 Rectangle bounds = captionInfo.getBounds();
716 if (bounds != null && bounds.contains(p)) {
717 return captionInfo;
718 }
719 }
720 }
721 for (int i = 0; i < elementInfo.getChildCount(); i++)
722 {
723 ElementInfo childInfo = elementInfo.getChild(i);
724 ElementInfo retValue = getElementInfoAt(childInfo, p);
725 if (retValue != null) {
726 return retValue;
727 }
728 }
729 }
730 return null;
731 }
732
733 /**
734 * Returns whether this object can accept focus or not. Objects that
735 * can accept focus will also have the AccessibleState.FOCUSABLE state
736 * set in their AccessibleStateSets.
737 *
738 * @return true if object can accept focus; otherwise false
739 * @see AccessibleContext#getAccessibleStateSet
740 * @see AccessibleState#FOCUSABLE
741 * @see AccessibleState#FOCUSED
742 * @see AccessibleStateSet
743 */
744 public boolean isFocusTraversable() {
745 Component comp = getTextComponent();
746 if (comp instanceof JTextComponent) {
747 if (((JTextComponent)comp).isEditable()) {
748 return true;
749 }
750 }
751 return false;
752 }
753
754 /**
755 * Requests focus for this object. If this object cannot accept focus,
756 * nothing will happen. Otherwise, the object will attempt to take
757 * focus.
758 * @see #isFocusTraversable
759 */
760 public void requestFocus() {
761 // TIGER - 4856191
762 if (! isFocusTraversable()) {
763 return;
764 }
765
766 Component comp = getTextComponent();
767 if (comp instanceof JTextComponent) {
768
769 comp.requestFocusInWindow();
770
771 try {
772 if (elementInfo.validateIfNecessary()) {
773 // set the caret position to the start of this component
774 Element elem = elementInfo.getElement();
775 ((JTextComponent)comp).setCaretPosition(elem.getStartOffset());
776
777 // fire a AccessibleState.FOCUSED property change event
778 AccessibleContext ac = editor.getAccessibleContext();
779 PropertyChangeEvent pce = new PropertyChangeEvent(this,
780 AccessibleContext.ACCESSIBLE_STATE_PROPERTY,
781 null, AccessibleState.FOCUSED);
782 ac.firePropertyChange(
783 AccessibleContext.ACCESSIBLE_STATE_PROPERTY,
784 null, pce);
785 }
786 } catch (IllegalArgumentException e) {
787 // don't fire property change event
788 }
789 }
790 }
791
792 /**
793 * Adds the specified focus listener to receive focus events from this
794 * component.
795 *
796 * @param l the focus listener
797 * @see #removeFocusListener
798 */
799 public void addFocusListener(FocusListener l) {
800 getTextComponent().addFocusListener(l);
801 }
802
803 /**
804 * Removes the specified focus listener so it no longer receives focus
805 * events from this component.
806 *
807 * @param l the focus listener
808 * @see #addFocusListener
809 */
810 public void removeFocusListener(FocusListener l) {
811 getTextComponent().removeFocusListener(l);
812 }
813 // ... end AccessibleComponent implementation
814 } // ... end HTMLAccessibleContext
815
816
817
818 /*
819 * ElementInfo for text
820 */
821 class TextElementInfo extends ElementInfo implements Accessible {
822
823 TextElementInfo(Element element, ElementInfo parent) {
824 super(element, parent);
825 }
826
827 // begin AccessibleText implementation ...
828 private AccessibleContext accessibleContext;
829
830 public AccessibleContext getAccessibleContext() {
831 if (accessibleContext == null) {
832 accessibleContext = new TextAccessibleContext(this);
833 }
834 return accessibleContext;
835 }
836
837 /*
838 * AccessibleContext for text elements
839 */
840 public class TextAccessibleContext extends HTMLAccessibleContext
841 implements AccessibleText {
842
843 public TextAccessibleContext(ElementInfo elementInfo) {
844 super(elementInfo);
845 }
846
847 public AccessibleText getAccessibleText() {
848 return this;
849 }
850
851 /**
852 * Gets the accessibleName property of this object. The accessibleName
853 * property of an object is a localized String that designates the purpose
854 * of the object. For example, the accessibleName property of a label
855 * or button might be the text of the label or button itself. In the
856 * case of an object that doesn't display its name, the accessibleName
857 * should still be set. For example, in the case of a text field used
858 * to enter the name of a city, the accessibleName for the en_US locale
859 * could be 'city.'
860 *
861 * @return the localized name of the object; null if this
862 * object does not have a name
863 *
864 * @see #setAccessibleName
865 */
866 public String getAccessibleName() {
867 if (model != null) {
868 return (String)model.getProperty(Document.TitleProperty);
869 } else {
870 return null;
871 }
872 }
873
874 /**
875 * Gets the accessibleDescription property of this object. If this
876 * property isn't set, returns the content type of this
877 * <code>JEditorPane</code> instead (e.g. "plain/text", "html/text").
878 *
879 * @return the localized description of the object; <code>null</code>
880 * if this object does not have a description
881 *
882 * @see #setAccessibleName
883 */
884 public String getAccessibleDescription() {
885 return editor.getContentType();
886 }
887
888 /**
889 * Gets the role of this object. The role of the object is the generic
890 * purpose or use of the class of this object. For example, the role
891 * of a push button is AccessibleRole.PUSH_BUTTON. The roles in
892 * AccessibleRole are provided so component developers can pick from
893 * a set of predefined roles. This enables assistive technologies to
894 * provide a consistent interface to various tweaked subclasses of
895 * components (e.g., use AccessibleRole.PUSH_BUTTON for all components
896 * that act like a push button) as well as distinguish between sublasses
897 * that behave differently (e.g., AccessibleRole.CHECK_BOX for check boxes
898 * and AccessibleRole.RADIO_BUTTON for radio buttons).
899 * <p>Note that the AccessibleRole class is also extensible, so
900 * custom component developers can define their own AccessibleRole's
901 * if the set of predefined roles is inadequate.
902 *
903 * @return an instance of AccessibleRole describing the role of the object
904 * @see AccessibleRole
905 */
906 public AccessibleRole getAccessibleRole() {
907 return AccessibleRole.TEXT;
908 }
909
910 /**
911 * Given a point in local coordinates, return the zero-based index
912 * of the character under that Point. If the point is invalid,
913 * this method returns -1.
914 *
915 * @param p the Point in local coordinates
916 * @return the zero-based index of the character under Point p; if
917 * Point is invalid returns -1.
918 */
919 public int getIndexAtPoint(Point p) {
920 View v = getView();
921 if (v != null) {
922 return v.viewToModel(p.x, p.y, getBounds());
923 } else {
924 return -1;
925 }
926 }
927
928 /**
929 * Determine the bounding box of the character at the given
930 * index into the string. The bounds are returned in local
931 * coordinates. If the index is invalid an empty rectangle is
932 * returned.
933 *
934 * @param i the index into the String
935 * @return the screen coordinates of the character's the bounding box,
936 * if index is invalid returns an empty rectangle.
937 */
938 public Rectangle getCharacterBounds(int i) {
939 try {
940 return editor.getUI().modelToView(editor, i);
941 } catch (BadLocationException e) {
942 return null;
943 }
944 }
945
946 /**
947 * Return the number of characters (valid indicies)
948 *
949 * @return the number of characters
950 */
951 public int getCharCount() {
952 if (validateIfNecessary()) {
953 Element elem = elementInfo.getElement();
954 return elem.getEndOffset() - elem.getStartOffset();
955 }
956 return 0;
957 }
958
959 /**
960 * Return the zero-based offset of the caret.
961 *
962 * Note: That to the right of the caret will have the same index
963 * value as the offset (the caret is between two characters).
964 * @return the zero-based offset of the caret.
965 */
966 public int getCaretPosition() {
967 View v = getView();
968 if (v == null) {
969 return -1;
970 }
971 Container c = v.getContainer();
972 if (c == null) {
973 return -1;
974 }
975 if (c instanceof JTextComponent) {
976 return ((JTextComponent)c).getCaretPosition();
977 } else {
978 return -1;
979 }
980 }
981
982 /**
983 * IndexedSegment extends Segment adding the offset into the
984 * the model the <code>Segment</code> was asked for.
985 */
986 private class IndexedSegment extends Segment {
987 /**
988 * Offset into the model that the position represents.
989 */
990 public int modelOffset;
991 }
992
993 public String getAtIndex(int part, int index) {
994 return getAtIndex(part, index, 0);
995 }
996
997
998 public String getAfterIndex(int part, int index) {
999 return getAtIndex(part, index, 1);
1000 }
1001
1002 public String getBeforeIndex(int part, int index) {
1003 return getAtIndex(part, index, -1);
1004 }
1005
1006 /**
1007 * Gets the word, sentence, or character at <code>index</code>.
1008 * If <code>direction</code> is non-null this will find the
1009 * next/previous word/sentence/character.
1010 */
1011 private String getAtIndex(int part, int index, int direction) {
1012 if (model instanceof AbstractDocument) {
1013 ((AbstractDocument)model).readLock();
1014 }
1015 try {
1016 if (index < 0 || index >= model.getLength()) {
1017 return null;
1018 }
1019 switch (part) {
1020 case AccessibleText.CHARACTER:
1021 if (index + direction < model.getLength() &&
1022 index + direction >= 0) {
1023 return model.getText(index + direction, 1);
1024 }
1025 break;
1026
1027
1028 case AccessibleText.WORD:
1029 case AccessibleText.SENTENCE:
1030 IndexedSegment seg = getSegmentAt(part, index);
1031 if (seg != null) {
1032 if (direction != 0) {
1033 int next;
1034
1035
1036 if (direction < 0) {
1037 next = seg.modelOffset - 1;
1038 }
1039 else {
1040 next = seg.modelOffset + direction * seg.count;
1041 }
1042 if (next >= 0 && next <= model.getLength()) {
1043 seg = getSegmentAt(part, next);
1044 }
1045 else {
1046 seg = null;
1047 }
1048 }
1049 if (seg != null) {
1050 return new String(seg.array, seg.offset,
1051 seg.count);
1052 }
1053 }
1054 break;
1055
1056 default:
1057 break;
1058 }
1059 } catch (BadLocationException e) {
1060 } finally {
1061 if (model instanceof AbstractDocument) {
1062 ((AbstractDocument)model).readUnlock();
1063 }
1064 }
1065 return null;
1066 }
1067
1068 /*
1069 * Returns the paragraph element for the specified index.
1070 */
1071 private Element getParagraphElement(int index) {
1072 if (model instanceof PlainDocument ) {
1073 PlainDocument sdoc = (PlainDocument)model;
1074 return sdoc.getParagraphElement(index);
1075 } else if (model instanceof StyledDocument) {
1076 StyledDocument sdoc = (StyledDocument)model;
1077 return sdoc.getParagraphElement(index);
1078 } else {
1079 Element para;
1080 for (para = model.getDefaultRootElement(); ! para.isLeaf(); ) {
1081 int pos = para.getElementIndex(index);
1082 para = para.getElement(pos);
1083 }
1084 if (para == null) {
1085 return null;
1086 }
1087 return para.getParentElement();
1088 }
1089 }
1090
1091 /*
1092 * Returns a <code>Segment</code> containing the paragraph text
1093 * at <code>index</code>, or null if <code>index</code> isn't
1094 * valid.
1095 */
1096 private IndexedSegment getParagraphElementText(int index)
1097 throws BadLocationException {
1098 Element para = getParagraphElement(index);
1099
1100
1101 if (para != null) {
1102 IndexedSegment segment = new IndexedSegment();
1103 try {
1104 int length = para.getEndOffset() - para.getStartOffset();
1105 model.getText(para.getStartOffset(), length, segment);
1106 } catch (BadLocationException e) {
1107 return null;
1108 }
1109 segment.modelOffset = para.getStartOffset();
1110 return segment;
1111 }
1112 return null;
1113 }
1114
1115
1116 /**
1117 * Returns the Segment at <code>index</code> representing either
1118 * the paragraph or sentence as identified by <code>part</code>, or
1119 * null if a valid paragraph/sentence can't be found. The offset
1120 * will point to the start of the word/sentence in the array, and
1121 * the modelOffset will point to the location of the word/sentence
1122 * in the model.
1123 */
1124 private IndexedSegment getSegmentAt(int part, int index)
1125 throws BadLocationException {
1126
1127 IndexedSegment seg = getParagraphElementText(index);
1128 if (seg == null) {
1129 return null;
1130 }
1131 BreakIterator iterator;
1132 switch (part) {
1133 case AccessibleText.WORD:
1134 iterator = BreakIterator.getWordInstance(getLocale());
1135 break;
1136 case AccessibleText.SENTENCE:
1137 iterator = BreakIterator.getSentenceInstance(getLocale());
1138 break;
1139 default:
1140 return null;
1141 }
1142 seg.first();
1143 iterator.setText(seg);
1144 int end = iterator.following(index - seg.modelOffset + seg.offset);
1145 if (end == BreakIterator.DONE) {
1146 return null;
1147 }
1148 if (end > seg.offset + seg.count) {
1149 return null;
1150 }
1151 int begin = iterator.previous();
1152 if (begin == BreakIterator.DONE ||
1153 begin >= seg.offset + seg.count) {
1154 return null;
1155 }
1156 seg.modelOffset = seg.modelOffset + begin - seg.offset;
1157 seg.offset = begin;
1158 seg.count = end - begin;
1159 return seg;
1160 }
1161
1162 /**
1163 * Return the AttributeSet for a given character at a given index
1164 *
1165 * @param i the zero-based index into the text
1166 * @return the AttributeSet of the character
1167 */
1168 public AttributeSet getCharacterAttribute(int i) {
1169 if (model instanceof StyledDocument) {
1170 StyledDocument doc = (StyledDocument)model;
1171 Element elem = doc.getCharacterElement(i);
1172 if (elem != null) {
1173 return elem.getAttributes();
1174 }
1175 }
1176 return null;
1177 }
1178
1179 /**
1180 * Returns the start offset within the selected text.
1181 * If there is no selection, but there is
1182 * a caret, the start and end offsets will be the same.
1183 *
1184 * @return the index into the text of the start of the selection
1185 */
1186 public int getSelectionStart() {
1187 return editor.getSelectionStart();
1188 }
1189
1190 /**
1191 * Returns the end offset within the selected text.
1192 * If there is no selection, but there is
1193 * a caret, the start and end offsets will be the same.
1194 *
1195 * @return the index into teh text of the end of the selection
1196 */
1197 public int getSelectionEnd() {
1198 return editor.getSelectionEnd();
1199 }
1200
1201 /**
1202 * Returns the portion of the text that is selected.
1203 *
1204 * @return the String portion of the text that is selected
1205 */
1206 public String getSelectedText() {
1207 return editor.getSelectedText();
1208 }
1209
1210 /*
1211 * Returns the text substring starting at the specified
1212 * offset with the specified length.
1213 */
1214 private String getText(int offset, int length)
1215 throws BadLocationException {
1216
1217 if (model != null && model instanceof StyledDocument) {
1218 StyledDocument doc = (StyledDocument)model;
1219 return model.getText(offset, length);
1220 } else {
1221 return null;
1222 }
1223 }
1224 }
1225 }
1226
1227 /*
1228 * ElementInfo for images
1229 */
1230 private class IconElementInfo extends ElementInfo implements Accessible {
1231
1232 private int width = -1;
1233 private int height = -1;
1234
1235 IconElementInfo(Element element, ElementInfo parent) {
1236 super(element, parent);
1237 }
1238
1239 protected void invalidate(boolean first) {
1240 super.invalidate(first);
1241 width = height = -1;
1242 }
1243
1244 private int getImageSize(Object key) {
1245 if (validateIfNecessary()) {
1246 int size = getIntAttr(getAttributes(), key, -1);
1247
1248 if (size == -1) {
1249 View v = getView();
1250
1251 size = 0;
1252 if (v instanceof ImageView) {
1253 Image img = ((ImageView)v).getImage();
1254 if (img != null) {
1255 if (key == HTML.Attribute.WIDTH) {
1256 size = img.getWidth(null);
1257 }
1258 else {
1259 size = img.getHeight(null);
1260 }
1261 }
1262 }
1263 }
1264 return size;
1265 }
1266 return 0;
1267 }
1268
1269 // begin AccessibleIcon implementation ...
1270 private AccessibleContext accessibleContext;
1271
1272 public AccessibleContext getAccessibleContext() {
1273 if (accessibleContext == null) {
1274 accessibleContext = new IconAccessibleContext(this);
1275 }
1276 return accessibleContext;
1277 }
1278
1279 /*
1280 * AccessibleContext for images
1281 */
1282 protected class IconAccessibleContext extends HTMLAccessibleContext
1283 implements AccessibleIcon {
1284
1285 public IconAccessibleContext(ElementInfo elementInfo) {
1286 super(elementInfo);
1287 }
1288
1289 /**
1290 * Gets the accessibleName property of this object. The accessibleName
1291 * property of an object is a localized String that designates the purpose
1292 * of the object. For example, the accessibleName property of a label
1293 * or button might be the text of the label or button itself. In the
1294 * case of an object that doesn't display its name, the accessibleName
1295 * should still be set. For example, in the case of a text field used
1296 * to enter the name of a city, the accessibleName for the en_US locale
1297 * could be 'city.'
1298 *
1299 * @return the localized name of the object; null if this
1300 * object does not have a name
1301 *
1302 * @see #setAccessibleName
1303 */
1304 public String getAccessibleName() {
1305 return getAccessibleIconDescription();
1306 }
1307
1308 /**
1309 * Gets the accessibleDescription property of this object. If this
1310 * property isn't set, returns the content type of this
1311 * <code>JEditorPane</code> instead (e.g. "plain/text", "html/text").
1312 *
1313 * @return the localized description of the object; <code>null</code>
1314 * if this object does not have a description
1315 *
1316 * @see #setAccessibleName
1317 */
1318 public String getAccessibleDescription() {
1319 return editor.getContentType();
1320 }
1321
1322 /**
1323 * Gets the role of this object. The role of the object is the generic
1324 * purpose or use of the class of this object. For example, the role
1325 * of a push button is AccessibleRole.PUSH_BUTTON. The roles in
1326 * AccessibleRole are provided so component developers can pick from
1327 * a set of predefined roles. This enables assistive technologies to
1328 * provide a consistent interface to various tweaked subclasses of
1329 * components (e.g., use AccessibleRole.PUSH_BUTTON for all components
1330 * that act like a push button) as well as distinguish between sublasses
1331 * that behave differently (e.g., AccessibleRole.CHECK_BOX for check boxes
1332 * and AccessibleRole.RADIO_BUTTON for radio buttons).
1333 * <p>Note that the AccessibleRole class is also extensible, so
1334 * custom component developers can define their own AccessibleRole's
1335 * if the set of predefined roles is inadequate.
1336 *
1337 * @return an instance of AccessibleRole describing the role of the object
1338 * @see AccessibleRole
1339 */
1340 public AccessibleRole getAccessibleRole() {
1341 return AccessibleRole.ICON;
1342 }
1343
1344 public AccessibleIcon [] getAccessibleIcon() {
1345 AccessibleIcon [] icons = new AccessibleIcon[1];
1346 icons[0] = this;
1347 return icons;
1348 }
1349
1350 /**
1351 * Gets the description of the icon. This is meant to be a brief
1352 * textual description of the object. For example, it might be
1353 * presented to a blind user to give an indication of the purpose
1354 * of the icon.
1355 *
1356 * @return the description of the icon
1357 */
1358 public String getAccessibleIconDescription() {
1359 return ((ImageView)getView()).getAltText();
1360 }
1361
1362 /**
1363 * Sets the description of the icon. This is meant to be a brief
1364 * textual description of the object. For example, it might be
1365 * presented to a blind user to give an indication of the purpose
1366 * of the icon.
1367 *
1368 * @param description the description of the icon
1369 */
1370 public void setAccessibleIconDescription(String description) {
1371 }
1372
1373 /**
1374 * Gets the width of the icon
1375 *
1376 * @return the width of the icon.
1377 */
1378 public int getAccessibleIconWidth() {
1379 if (width == -1) {
1380 width = getImageSize(HTML.Attribute.WIDTH);
1381 }
1382 return width;
1383 }
1384
1385 /**
1386 * Gets the height of the icon
1387 *
1388 * @return the height of the icon.
1389 */
1390 public int getAccessibleIconHeight() {
1391 if (height == -1) {
1392 height = getImageSize(HTML.Attribute.HEIGHT);
1393 }
1394 return height;
1395 }
1396 }
1397 // ... end AccessibleIconImplementation
1398 }
1399
1400
1401 /**
1402 * TableElementInfo encapsulates information about a HTML.Tag.TABLE.
1403 * To make access fast it crates a grid containing the children to
1404 * allow for access by row, column. TableElementInfo will contain
1405 * TableRowElementInfos, which will contain TableCellElementInfos.
1406 * Any time one of the rows or columns becomes invalid the table is
1407 * invalidated. This is because any time one of the child attributes
1408 * changes the size of the grid may have changed.
1409 */
1410 private class TableElementInfo extends ElementInfo
1411 implements Accessible {
1412
1413 protected ElementInfo caption;
1414
1415 /**
1416 * Allocation of the table by row x column. There may be holes (eg
1417 * nulls) depending upon the html, any cell that has a rowspan/colspan
1418 * > 1 will be contained multiple times in the grid.
1419 */
1420 private TableCellElementInfo[][] grid;
1421
1422
1423 TableElementInfo(Element e, ElementInfo parent) {
1424 super(e, parent);
1425 }
1426
1427 public ElementInfo getCaptionInfo() {
1428 return caption;
1429 }
1430
1431 /**
1432 * Overriden to update the grid when validating.
1433 */
1434 protected void validate() {
1435 super.validate();
1436 updateGrid();
1437 }
1438
1439 /**
1440 * Overriden to only alloc instances of TableRowElementInfos.
1441 */
1442 protected void loadChildren(Element e) {
1443
1444 for (int counter = 0; counter < e.getElementCount(); counter++) {
1445 Element child = e.getElement(counter);
1446 AttributeSet attrs = child.getAttributes();
1447
1448 if (attrs.getAttribute(StyleConstants.NameAttribute) ==
1449 HTML.Tag.TR) {
1450 addChild(new TableRowElementInfo(child, this, counter));
1451
1452 } else if (attrs.getAttribute(StyleConstants.NameAttribute) ==
1453 HTML.Tag.CAPTION) {
1454 // Handle captions as a special case since all other
1455 // children are table rows.
1456 caption = createElementInfo(child, this);
1457 }
1458 }
1459 }
1460
1461 /**
1462 * Updates the grid.
1463 */
1464 private void updateGrid() {
1465 // Determine the max row/col count.
1466 int delta = 0;
1467 int maxCols = 0;
1468 int rows;
1469 for (int counter = 0; counter < getChildCount(); counter++) {
1470 TableRowElementInfo row = getRow(counter);
1471 int prev = 0;
1472 for (int y = 0; y < delta; y++) {
1473 prev = Math.max(prev, getRow(counter - y - 1).
1474 getColumnCount(y + 2));
1475 }
1476 delta = Math.max(row.getRowCount(), delta);
1477 delta--;
1478 maxCols = Math.max(maxCols, row.getColumnCount() + prev);
1479 }
1480 rows = getChildCount() + delta;
1481
1482 // Alloc
1483 grid = new TableCellElementInfo[rows][];
1484 for (int counter = 0; counter < rows; counter++) {
1485 grid[counter] = new TableCellElementInfo[maxCols];
1486 }
1487 // Update
1488 for (int counter = 0; counter < rows; counter++) {
1489 getRow(counter).updateGrid(counter);
1490 }
1491 }
1492
1493 /**
1494 * Returns the TableCellElementInfo at the specified index.
1495 */
1496 public TableRowElementInfo getRow(int index) {
1497 return (TableRowElementInfo)getChild(index);
1498 }
1499
1500 /**
1501 * Returns the TableCellElementInfo by row and column.
1502 */
1503 public TableCellElementInfo getCell(int r, int c) {
1504 if (validateIfNecessary() && r < grid.length &&
1505 c < grid[0].length) {
1506 return grid[r][c];
1507 }
1508 return null;
1509 }
1510
1511 /**
1512 * Returns the rowspan of the specified entry.
1513 */
1514 public int getRowExtentAt(int r, int c) {
1515 TableCellElementInfo cell = getCell(r, c);
1516
1517 if (cell != null) {
1518 int rows = cell.getRowCount();
1519 int delta = 1;
1520
1521 while ((r - delta) >= 0 && grid[r - delta][c] == cell) {
1522 delta++;
1523 }
1524 return rows - delta + 1;
1525 }
1526 return 0;
1527 }
1528
1529 /**
1530 * Returns the colspan of the specified entry.
1531 */
1532 public int getColumnExtentAt(int r, int c) {
1533 TableCellElementInfo cell = getCell(r, c);
1534
1535 if (cell != null) {
1536 int cols = cell.getColumnCount();
1537 int delta = 1;
1538
1539 while ((c - delta) >= 0 && grid[r][c - delta] == cell) {
1540 delta++;
1541 }
1542 return cols - delta + 1;
1543 }
1544 return 0;
1545 }
1546
1547 /**
1548 * Returns the number of rows in the table.
1549 */
1550 public int getRowCount() {
1551 if (validateIfNecessary()) {
1552 return grid.length;
1553 }
1554 return 0;
1555 }
1556
1557 /**
1558 * Returns the number of columns in the table.
1559 */
1560 public int getColumnCount() {
1561 if (validateIfNecessary() && grid.length > 0) {
1562 return grid[0].length;
1563 }
1564 return 0;
1565 }
1566
1567 // begin AccessibleTable implementation ...
1568 private AccessibleContext accessibleContext;
1569
1570 public AccessibleContext getAccessibleContext() {
1571 if (accessibleContext == null) {
1572 accessibleContext = new TableAccessibleContext(this);
1573 }
1574 return accessibleContext;
1575 }
1576
1577 /*
1578 * AccessibleContext for tables
1579 */
1580 public class TableAccessibleContext extends HTMLAccessibleContext
1581 implements AccessibleTable {
1582
1583 private AccessibleHeadersTable rowHeadersTable;
1584
1585 public TableAccessibleContext(ElementInfo elementInfo) {
1586 super(elementInfo);
1587 }
1588
1589 /**
1590 * Gets the accessibleName property of this object. The accessibleName
1591 * property of an object is a localized String that designates the purpose
1592 * of the object. For example, the accessibleName property of a label
1593 * or button might be the text of the label or button itself. In the
1594 * case of an object that doesn't display its name, the accessibleName
1595 * should still be set. For example, in the case of a text field used
1596 * to enter the name of a city, the accessibleName for the en_US locale
1597 * could be 'city.'
1598 *
1599 * @return the localized name of the object; null if this
1600 * object does not have a name
1601 *
1602 * @see #setAccessibleName
1603 */
1604 public String getAccessibleName() {
1605 // return the role of the object
1606 return getAccessibleRole().toString();
1607 }
1608
1609 /**
1610 * Gets the accessibleDescription property of this object. If this
1611 * property isn't set, returns the content type of this
1612 * <code>JEditorPane</code> instead (e.g. "plain/text", "html/text").
1613 *
1614 * @return the localized description of the object; <code>null</code>
1615 * if this object does not have a description
1616 *
1617 * @see #setAccessibleName
1618 */
1619 public String getAccessibleDescription() {
1620 return editor.getContentType();
1621 }
1622
1623 /**
1624 * Gets the role of this object. The role of the object is the generic
1625 * purpose or use of the class of this object. For example, the role
1626 * of a push button is AccessibleRole.PUSH_BUTTON. The roles in
1627 * AccessibleRole are provided so component developers can pick from
1628 * a set of predefined roles. This enables assistive technologies to
1629 * provide a consistent interface to various tweaked subclasses of
1630 * components (e.g., use AccessibleRole.PUSH_BUTTON for all components
1631 * that act like a push button) as well as distinguish between sublasses
1632 * that behave differently (e.g., AccessibleRole.CHECK_BOX for check boxes
1633 * and AccessibleRole.RADIO_BUTTON for radio buttons).
1634 * <p>Note that the AccessibleRole class is also extensible, so
1635 * custom component developers can define their own AccessibleRole's
1636 * if the set of predefined roles is inadequate.
1637 *
1638 * @return an instance of AccessibleRole describing the role of the object
1639 * @see AccessibleRole
1640 */
1641 public AccessibleRole getAccessibleRole() {
1642 return AccessibleRole.TABLE;
1643 }
1644
1645 /**
1646 * Gets the 0-based index of this object in its accessible parent.
1647 *
1648 * @return the 0-based index of this object in its parent; -1 if this
1649 * object does not have an accessible parent.
1650 *
1651 * @see #getAccessibleParent
1652 * @see #getAccessibleChildrenCount
1653 * @gsee #getAccessibleChild
1654 */
1655 public int getAccessibleIndexInParent() {
1656 return elementInfo.getIndexInParent();
1657 }
1658
1659 /**
1660 * Returns the number of accessible children of the object.
1661 *
1662 * @return the number of accessible children of the object.
1663 */
1664 public int getAccessibleChildrenCount() {
1665 return ((TableElementInfo)elementInfo).getRowCount() *
1666 ((TableElementInfo)elementInfo).getColumnCount();
1667 }
1668
1669 /**
1670 * Returns the specified Accessible child of the object. The Accessible
1671 * children of an Accessible object are zero-based, so the first child
1672 * of an Accessible child is at index 0, the second child is at index 1,
1673 * and so on.
1674 *
1675 * @param i zero-based index of child
1676 * @return the Accessible child of the object
1677 * @see #getAccessibleChildrenCount
1678 */
1679 public Accessible getAccessibleChild(int i) {
1680 int rowCount = ((TableElementInfo)elementInfo).getRowCount();
1681 int columnCount = ((TableElementInfo)elementInfo).getColumnCount();
1682 int r = i / rowCount;
1683 int c = i % columnCount;
1684 if (r < 0 || r >= rowCount || c < 0 || c >= columnCount) {
1685 return null;
1686 } else {
1687 return getAccessibleAt(r, c);
1688 }
1689 }
1690
1691 public AccessibleTable getAccessibleTable() {
1692 return this;
1693 }
1694
1695 /**
1696 * Returns the caption for the table.
1697 *
1698 * @return the caption for the table
1699 */
1700 public Accessible getAccessibleCaption() {
1701 ElementInfo captionInfo = getCaptionInfo();
1702 if (captionInfo instanceof Accessible) {
1703 return (Accessible)caption;
1704 } else {
1705 return null;
1706 }
1707 }
1708
1709 /**
1710 * Sets the caption for the table.
1711 *
1712 * @param a the caption for the table
1713 */
1714 public void setAccessibleCaption(Accessible a) {
1715 }
1716
1717 /**
1718 * Returns the summary description of the table.
1719 *
1720 * @return the summary description of the table
1721 */
1722 public Accessible getAccessibleSummary() {
1723 return null;
1724 }
1725
1726 /**
1727 * Sets the summary description of the table
1728 *
1729 * @param a the summary description of the table
1730 */
1731 public void setAccessibleSummary(Accessible a) {
1732 }
1733
1734 /**
1735 * Returns the number of rows in the table.
1736 *
1737 * @return the number of rows in the table
1738 */
1739 public int getAccessibleRowCount() {
1740 return ((TableElementInfo)elementInfo).getRowCount();
1741 }
1742
1743 /**
1744 * Returns the number of columns in the table.
1745 *
1746 * @return the number of columns in the table
1747 */
1748 public int getAccessibleColumnCount() {
1749 return ((TableElementInfo)elementInfo).getColumnCount();
1750 }
1751
1752 /**
1753 * Returns the Accessible at a specified row and column
1754 * in the table.
1755 *
1756 * @param r zero-based row of the table
1757 * @param c zero-based column of the table
1758 * @return the Accessible at the specified row and column
1759 */
1760 public Accessible getAccessibleAt(int r, int c) {
1761 TableCellElementInfo cellInfo = getCell(r, c);
1762 if (cellInfo != null) {
1763 return cellInfo.getAccessible();
1764 } else {
1765 return null;
1766 }
1767 }
1768
1769 /**
1770 * Returns the number of rows occupied by the Accessible at
1771 * a specified row and column in the table.
1772 *
1773 * @return the number of rows occupied by the Accessible at a
1774 * given specified (row, column)
1775 */
1776 public int getAccessibleRowExtentAt(int r, int c) {
1777 return ((TableElementInfo)elementInfo).getRowExtentAt(r, c);
1778 }
1779
1780 /**
1781 * Returns the number of columns occupied by the Accessible at
1782 * a specified row and column in the table.
1783 *
1784 * @return the number of columns occupied by the Accessible at a
1785 * given specified row and column
1786 */
1787 public int getAccessibleColumnExtentAt(int r, int c) {
1788 return ((TableElementInfo)elementInfo).getColumnExtentAt(r, c);
1789 }
1790
1791 /**
1792 * Returns the row headers as an AccessibleTable.
1793 *
1794 * @return an AccessibleTable representing the row
1795 * headers
1796 */
1797 public AccessibleTable getAccessibleRowHeader() {
1798 return rowHeadersTable;
1799 }
1800
1801 /**
1802 * Sets the row headers.
1803 *
1804 * @param table an AccessibleTable representing the
1805 * row headers
1806 */
1807 public void setAccessibleRowHeader(AccessibleTable table) {
1808 }
1809
1810 /**
1811 * Returns the column headers as an AccessibleTable.
1812 *
1813 * @return an AccessibleTable representing the column
1814 * headers
1815 */
1816 public AccessibleTable getAccessibleColumnHeader() {
1817 return null;
1818 }
1819
1820 /**
1821 * Sets the column headers.
1822 *
1823 * @param table an AccessibleTable representing the
1824 * column headers
1825 */
1826 public void setAccessibleColumnHeader(AccessibleTable table) {
1827 }
1828
1829 /**
1830 * Returns the description of the specified row in the table.
1831 *
1832 * @param r zero-based row of the table
1833 * @return the description of the row
1834 */
1835 public Accessible getAccessibleRowDescription(int r) {
1836 return null;
1837 }
1838
1839 /**
1840 * Sets the description text of the specified row of the table.
1841 *
1842 * @param r zero-based row of the table
1843 * @param a the description of the row
1844 */
1845 public void setAccessibleRowDescription(int r, Accessible a) {
1846 }
1847
1848 /**
1849 * Returns the description text of the specified column in the table.
1850 *
1851 * @param c zero-based column of the table
1852 * @return the text description of the column
1853 */
1854 public Accessible getAccessibleColumnDescription(int c) {
1855 return null;
1856 }
1857
1858 /**
1859 * Sets the description text of the specified column in the table.
1860 *
1861 * @param c zero-based column of the table
1862 * @param a the text description of the column
1863 */
1864 public void setAccessibleColumnDescription(int c, Accessible a) {
1865 }
1866
1867 /**
1868 * Returns a boolean value indicating whether the accessible at
1869 * a specified row and column is selected.
1870 *
1871 * @param r zero-based row of the table
1872 * @param c zero-based column of the table
1873 * @return the boolean value true if the accessible at the
1874 * row and column is selected. Otherwise, the boolean value
1875 * false
1876 */
1877 public boolean isAccessibleSelected(int r, int c) {
1878 if (validateIfNecessary()) {
1879 if (r < 0 || r >= getAccessibleRowCount() ||
1880 c < 0 || c >= getAccessibleColumnCount()) {
1881 return false;
1882 }
1883 TableCellElementInfo cell = getCell(r, c);
1884 if (cell != null) {
1885 Element elem = cell.getElement();
1886 int start = elem.getStartOffset();
1887 int end = elem.getEndOffset();
1888 return start >= editor.getSelectionStart() &&
1889 end <= editor.getSelectionEnd();
1890 }
1891 }
1892 return false;
1893 }
1894
1895 /**
1896 * Returns a boolean value indicating whether the specified row
1897 * is selected.
1898 *
1899 * @param r zero-based row of the table
1900 * @return the boolean value true if the specified row is selected.
1901 * Otherwise, false.
1902 */
1903 public boolean isAccessibleRowSelected(int r) {
1904 if (validateIfNecessary()) {
1905 if (r < 0 || r >= getAccessibleRowCount()) {
1906 return false;
1907 }
1908 int nColumns = getAccessibleColumnCount();
1909
1910 TableCellElementInfo startCell = getCell(r, 0);
1911 if (startCell == null) {
1912 return false;
1913 }
1914 int start = startCell.getElement().getStartOffset();
1915
1916 TableCellElementInfo endCell = getCell(r, nColumns-1);
1917 if (endCell == null) {
1918 return false;
1919 }
1920 int end = endCell.getElement().getEndOffset();
1921
1922 return start >= editor.getSelectionStart() &&
1923 end <= editor.getSelectionEnd();
1924 }
1925 return false;
1926 }
1927
1928 /**
1929 * Returns a boolean value indicating whether the specified column
1930 * is selected.
1931 *
1932 * @param c zero-based column of the table
1933 * @return the boolean value true if the specified column is selected.
1934 * Otherwise, false.
1935 */
1936 public boolean isAccessibleColumnSelected(int c) {
1937 if (validateIfNecessary()) {
1938 if (c < 0 || c >= getAccessibleColumnCount()) {
1939 return false;
1940 }
1941 int nRows = getAccessibleRowCount();
1942
1943 TableCellElementInfo startCell = getCell(0, c);
1944 if (startCell == null) {
1945 return false;
1946 }
1947 int start = startCell.getElement().getStartOffset();
1948
1949 TableCellElementInfo endCell = getCell(nRows-1, c);
1950 if (endCell == null) {
1951 return false;
1952 }
1953 int end = endCell.getElement().getEndOffset();
1954 return start >= editor.getSelectionStart() &&
1955 end <= editor.getSelectionEnd();
1956 }
1957 return false;
1958 }
1959
1960 /**
1961 * Returns the selected rows in a table.
1962 *
1963 * @return an array of selected rows where each element is a
1964 * zero-based row of the table
1965 */
1966 public int [] getSelectedAccessibleRows() {
1967 if (validateIfNecessary()) {
1968 int nRows = getAccessibleRowCount();
1969 Vector<Integer> vec = new Vector<Integer>();
1970
1971 for (int i = 0; i < nRows; i++) {
1972 if (isAccessibleRowSelected(i)) {
1973 vec.addElement(Integer.valueOf(i));
1974 }
1975 }
1976 int retval[] = new int[vec.size()];
1977 for (int i = 0; i < retval.length; i++) {
1978 retval[i] = vec.elementAt(i).intValue();
1979 }
1980 return retval;
1981 }
1982 return new int[0];
1983 }
1984
1985 /**
1986 * Returns the selected columns in a table.
1987 *
1988 * @return an array of selected columns where each element is a
1989 * zero-based column of the table
1990 */
1991 public int [] getSelectedAccessibleColumns() {
1992 if (validateIfNecessary()) {
1993 int nColumns = getAccessibleRowCount();
1994 Vector<Integer> vec = new Vector<Integer>();
1995
1996 for (int i = 0; i < nColumns; i++) {
1997 if (isAccessibleColumnSelected(i)) {
1998 vec.addElement(Integer.valueOf(i));
1999 }
2000 }
2001 int retval[] = new int[vec.size()];
2002 for (int i = 0; i < retval.length; i++) {
2003 retval[i] = vec.elementAt(i).intValue();
2004 }
2005 return retval;
2006 }
2007 return new int[0];
2008 }
2009
2010 // begin AccessibleExtendedTable implementation -------------
2011
2012 /**
2013 * Returns the row number of an index in the table.
2014 *
2015 * @param index the zero-based index in the table
2016 * @return the zero-based row of the table if one exists;
2017 * otherwise -1.
2018 */
2019 public int getAccessibleRow(int index) {
2020 if (validateIfNecessary()) {
2021 int numCells = getAccessibleColumnCount() *
2022 getAccessibleRowCount();
2023 if (index >= numCells) {
2024 return -1;
2025 } else {
2026 return index / getAccessibleColumnCount();
2027 }
2028 }
2029 return -1;
2030 }
2031
2032 /**
2033 * Returns the column number of an index in the table.
2034 *
2035 * @param index the zero-based index in the table
2036 * @return the zero-based column of the table if one exists;
2037 * otherwise -1.
2038 */
2039 public int getAccessibleColumn(int index) {
2040 if (validateIfNecessary()) {
2041 int numCells = getAccessibleColumnCount() *
2042 getAccessibleRowCount();
2043 if (index >= numCells) {
2044 return -1;
2045 } else {
2046 return index % getAccessibleColumnCount();
2047 }
2048 }
2049 return -1;
2050 }
2051
2052 /**
2053 * Returns the index at a row and column in the table.
2054 *
2055 * @param r zero-based row of the table
2056 * @param c zero-based column of the table
2057 * @return the zero-based index in the table if one exists;
2058 * otherwise -1.
2059 */
2060 public int getAccessibleIndex(int r, int c) {
2061 if (validateIfNecessary()) {
2062 if (r >= getAccessibleRowCount() ||
2063 c >= getAccessibleColumnCount()) {
2064 return -1;
2065 } else {
2066 return r * getAccessibleColumnCount() + c;
2067 }
2068 }
2069 return -1;
2070 }
2071
2072 /**
2073 * Returns the row header at a row in a table.
2074 * @param r zero-based row of the table
2075 *
2076 * @return a String representing the row header
2077 * if one exists; otherwise null.
2078 */
2079 public String getAccessibleRowHeader(int r) {
2080 if (validateIfNecessary()) {
2081 TableCellElementInfo cellInfo = getCell(r, 0);
2082 if (cellInfo.isHeaderCell()) {
2083 View v = cellInfo.getView();
2084 if (v != null && model != null) {
2085 try {
2086 return model.getText(v.getStartOffset(),
2087 v.getEndOffset() -
2088 v.getStartOffset());
2089 } catch (BadLocationException e) {
2090 return null;
2091 }
2092 }
2093 }
2094 }
2095 return null;
2096 }
2097
2098 /**
2099 * Returns the column header at a column in a table.
2100 * @param c zero-based column of the table
2101 *
2102 * @return a String representing the column header
2103 * if one exists; otherwise null.
2104 */
2105 public String getAccessibleColumnHeader(int c) {
2106 if (validateIfNecessary()) {
2107 TableCellElementInfo cellInfo = getCell(0, c);
2108 if (cellInfo.isHeaderCell()) {
2109 View v = cellInfo.getView();
2110 if (v != null && model != null) {
2111 try {
2112 return model.getText(v.getStartOffset(),
2113 v.getEndOffset() -
2114 v.getStartOffset());
2115 } catch (BadLocationException e) {
2116 return null;
2117 }
2118 }
2119 }
2120 }
2121 return null;
2122 }
2123
2124 public void addRowHeader(TableCellElementInfo cellInfo, int rowNumber) {
2125 if (rowHeadersTable == null) {
2126 rowHeadersTable = new AccessibleHeadersTable();
2127 }
2128 rowHeadersTable.addHeader(cellInfo, rowNumber);
2129 }
2130 // end of AccessibleExtendedTable implementation ------------
2131
2132 protected class AccessibleHeadersTable implements AccessibleTable {
2133
2134 // Header information is modeled as a Hashtable of
2135 // ArrayLists where each Hashtable entry represents
2136 // a row containing one or more headers.
2137 private Hashtable<Integer, ArrayList<TableCellElementInfo>> headers =
2138 new Hashtable<Integer, ArrayList<TableCellElementInfo>>();
2139 private int rowCount = 0;
2140 private int columnCount = 0;
2141
2142 public void addHeader(TableCellElementInfo cellInfo, int rowNumber) {
2143 Integer rowInteger = Integer.valueOf(rowNumber);
2144 ArrayList<TableCellElementInfo> list = headers.get(rowInteger);
2145 if (list == null) {
2146 list = new ArrayList<TableCellElementInfo>();
2147 headers.put(rowInteger, list);
2148 }
2149 list.add(cellInfo);
2150 }
2151
2152 /**
2153 * Returns the caption for the table.
2154 *
2155 * @return the caption for the table
2156 */
2157 public Accessible getAccessibleCaption() {
2158 return null;
2159 }
2160
2161 /**
2162 * Sets the caption for the table.
2163 *
2164 * @param a the caption for the table
2165 */
2166 public void setAccessibleCaption(Accessible a) {
2167 }
2168
2169 /**
2170 * Returns the summary description of the table.
2171 *
2172 * @return the summary description of the table
2173 */
2174 public Accessible getAccessibleSummary() {
2175 return null;
2176 }
2177
2178 /**
2179 * Sets the summary description of the table
2180 *
2181 * @param a the summary description of the table
2182 */
2183 public void setAccessibleSummary(Accessible a) {
2184 }
2185
2186 /**
2187 * Returns the number of rows in the table.
2188 *
2189 * @return the number of rows in the table
2190 */
2191 public int getAccessibleRowCount() {
2192 return rowCount;
2193 }
2194
2195 /**
2196 * Returns the number of columns in the table.
2197 *
2198 * @return the number of columns in the table
2199 */
2200 public int getAccessibleColumnCount() {
2201 return columnCount;
2202 }
2203
2204 private TableCellElementInfo getElementInfoAt(int r, int c) {
2205 ArrayList<TableCellElementInfo> list = headers.get(Integer.valueOf(r));
2206 if (list != null) {
2207 return list.get(c);
2208 } else {
2209 return null;
2210 }
2211 }
2212
2213 /**
2214 * Returns the Accessible at a specified row and column
2215 * in the table.
2216 *
2217 * @param r zero-based row of the table
2218 * @param c zero-based column of the table
2219 * @return the Accessible at the specified row and column
2220 */
2221 public Accessible getAccessibleAt(int r, int c) {
2222 ElementInfo elementInfo = getElementInfoAt(r, c);
2223 if (elementInfo instanceof Accessible) {
2224 return (Accessible)elementInfo;
2225 } else {
2226 return null;
2227 }
2228 }
2229
2230 /**
2231 * Returns the number of rows occupied by the Accessible at
2232 * a specified row and column in the table.
2233 *
2234 * @return the number of rows occupied by the Accessible at a
2235 * given specified (row, column)
2236 */
2237 public int getAccessibleRowExtentAt(int r, int c) {
2238 TableCellElementInfo elementInfo = getElementInfoAt(r, c);
2239 if (elementInfo != null) {
2240 return elementInfo.getRowCount();
2241 } else {
2242 return 0;
2243 }
2244 }
2245
2246 /**
2247 * Returns the number of columns occupied by the Accessible at
2248 * a specified row and column in the table.
2249 *
2250 * @return the number of columns occupied by the Accessible at a
2251 * given specified row and column
2252 */
2253 public int getAccessibleColumnExtentAt(int r, int c) {
2254 TableCellElementInfo elementInfo = getElementInfoAt(r, c);
2255 if (elementInfo != null) {
2256 return elementInfo.getRowCount();
2257 } else {
2258 return 0;
2259 }
2260 }
2261
2262 /**
2263 * Returns the row headers as an AccessibleTable.
2264 *
2265 * @return an AccessibleTable representing the row
2266 * headers
2267 */
2268 public AccessibleTable getAccessibleRowHeader() {
2269 return null;
2270 }
2271
2272 /**
2273 * Sets the row headers.
2274 *
2275 * @param table an AccessibleTable representing the
2276 * row headers
2277 */
2278 public void setAccessibleRowHeader(AccessibleTable table) {
2279 }
2280
2281 /**
2282 * Returns the column headers as an AccessibleTable.
2283 *
2284 * @return an AccessibleTable representing the column
2285 * headers
2286 */
2287 public AccessibleTable getAccessibleColumnHeader() {
2288 return null;
2289 }
2290
2291 /**
2292 * Sets the column headers.
2293 *
2294 * @param table an AccessibleTable representing the
2295 * column headers
2296 */
2297 public void setAccessibleColumnHeader(AccessibleTable table) {
2298 }
2299
2300 /**
2301 * Returns the description of the specified row in the table.
2302 *
2303 * @param r zero-based row of the table
2304 * @return the description of the row
2305 */
2306 public Accessible getAccessibleRowDescription(int r) {
2307 return null;
2308 }
2309
2310 /**
2311 * Sets the description text of the specified row of the table.
2312 *
2313 * @param r zero-based row of the table
2314 * @param a the description of the row
2315 */
2316 public void setAccessibleRowDescription(int r, Accessible a) {
2317 }
2318
2319 /**
2320 * Returns the description text of the specified column in the table.
2321 *
2322 * @param c zero-based column of the table
2323 * @return the text description of the column
2324 */
2325 public Accessible getAccessibleColumnDescription(int c) {
2326 return null;
2327 }
2328
2329 /**
2330 * Sets the description text of the specified column in the table.
2331 *
2332 * @param c zero-based column of the table
2333 * @param a the text description of the column
2334 */
2335 public void setAccessibleColumnDescription(int c, Accessible a) {
2336 }
2337
2338 /**
2339 * Returns a boolean value indicating whether the accessible at
2340 * a specified row and column is selected.
2341 *
2342 * @param r zero-based row of the table
2343 * @param c zero-based column of the table
2344 * @return the boolean value true if the accessible at the
2345 * row and column is selected. Otherwise, the boolean value
2346 * false
2347 */
2348 public boolean isAccessibleSelected(int r, int c) {
2349 return false;
2350 }
2351
2352 /**
2353 * Returns a boolean value indicating whether the specified row
2354 * is selected.
2355 *
2356 * @param r zero-based row of the table
2357 * @return the boolean value true if the specified row is selected.
2358 * Otherwise, false.
2359 */
2360 public boolean isAccessibleRowSelected(int r) {
2361 return false;
2362 }
2363
2364 /**
2365 * Returns a boolean value indicating whether the specified column
2366 * is selected.
2367 *
2368 * @param c zero-based column of the table
2369 * @return the boolean value true if the specified column is selected.
2370 * Otherwise, false.
2371 */
2372 public boolean isAccessibleColumnSelected(int c) {
2373 return false;
2374 }
2375
2376 /**
2377 * Returns the selected rows in a table.
2378 *
2379 * @return an array of selected rows where each element is a
2380 * zero-based row of the table
2381 */
2382 public int [] getSelectedAccessibleRows() {
2383 return new int [0];
2384 }
2385
2386 /**
2387 * Returns the selected columns in a table.
2388 *
2389 * @return an array of selected columns where each element is a
2390 * zero-based column of the table
2391 */
2392 public int [] getSelectedAccessibleColumns() {
2393 return new int [0];
2394 }
2395 }
2396 } // ... end AccessibleHeadersTable
2397
2398 /*
2399 * ElementInfo for table rows
2400 */
2401 private class TableRowElementInfo extends ElementInfo {
2402
2403 private TableElementInfo parent;
2404 private int rowNumber;
2405
2406 TableRowElementInfo(Element e, TableElementInfo parent, int rowNumber) {
2407 super(e, parent);
2408 this.parent = parent;
2409 this.rowNumber = rowNumber;
2410 }
2411
2412 protected void loadChildren(Element e) {
2413 for (int x = 0; x < e.getElementCount(); x++) {
2414 AttributeSet attrs = e.getElement(x).getAttributes();
2415
2416 if (attrs.getAttribute(StyleConstants.NameAttribute) ==
2417 HTML.Tag.TH) {
2418 TableCellElementInfo headerElementInfo =
2419 new TableCellElementInfo(e.getElement(x), this, true);
2420 addChild(headerElementInfo);
2421
2422 AccessibleTable at =
2423 parent.getAccessibleContext().getAccessibleTable();
2424 TableAccessibleContext tableElement =
2425 (TableAccessibleContext)at;
2426 tableElement.addRowHeader(headerElementInfo, rowNumber);
2427
2428 } else if (attrs.getAttribute(StyleConstants.NameAttribute) ==
2429 HTML.Tag.TD) {
2430 addChild(new TableCellElementInfo(e.getElement(x), this,
2431 false));
2432 }
2433 }
2434 }
2435
2436 /**
2437 * Returns the max of the rowspans of the cells in this row.
2438 */
2439 public int getRowCount() {
2440 int rowCount = 1;
2441 if (validateIfNecessary()) {
2442 for (int counter = 0; counter < getChildCount();
2443 counter++) {
2444
2445 TableCellElementInfo cell = (TableCellElementInfo)
2446 getChild(counter);
2447
2448 if (cell.validateIfNecessary()) {
2449 rowCount = Math.max(rowCount, cell.getRowCount());
2450 }
2451 }
2452 }
2453 return rowCount;
2454 }
2455
2456 /**
2457 * Returns the sum of the column spans of the individual
2458 * cells in this row.
2459 */
2460 public int getColumnCount() {
2461 int colCount = 0;
2462 if (validateIfNecessary()) {
2463 for (int counter = 0; counter < getChildCount();
2464 counter++) {
2465 TableCellElementInfo cell = (TableCellElementInfo)
2466 getChild(counter);
2467
2468 if (cell.validateIfNecessary()) {
2469 colCount += cell.getColumnCount();
2470 }
2471 }
2472 }
2473 return colCount;
2474 }
2475
2476 /**
2477 * Overriden to invalidate the table as well as
2478 * TableRowElementInfo.
2479 */
2480 protected void invalidate(boolean first) {
2481 super.invalidate(first);
2482 getParent().invalidate(true);
2483 }
2484
2485 /**
2486 * Places the TableCellElementInfos for this element in
2487 * the grid.
2488 */
2489 private void updateGrid(int row) {
2490 if (validateIfNecessary()) {
2491 boolean emptyRow = false;
2492
2493 while (!emptyRow) {
2494 for (int counter = 0; counter < grid[row].length;
2495 counter++) {
2496 if (grid[row][counter] == null) {
2497 emptyRow = true;
2498 break;
2499 }
2500 }
2501 if (!emptyRow) {
2502 row++;
2503 }
2504 }
2505 for (int col = 0, counter = 0; counter < getChildCount();
2506 counter++) {
2507 TableCellElementInfo cell = (TableCellElementInfo)
2508 getChild(counter);
2509
2510 while (grid[row][col] != null) {
2511 col++;
2512 }
2513 for (int rowCount = cell.getRowCount() - 1;
2514 rowCount >= 0; rowCount--) {
2515 for (int colCount = cell.getColumnCount() - 1;
2516 colCount >= 0; colCount--) {
2517 grid[row + rowCount][col + colCount] = cell;
2518 }
2519 }
2520 col += cell.getColumnCount();
2521 }
2522 }
2523 }
2524
2525 /**
2526 * Returns the column count of the number of columns that have
2527 * a rowcount >= rowspan.
2528 */
2529 private int getColumnCount(int rowspan) {
2530 if (validateIfNecessary()) {
2531 int cols = 0;
2532 for (int counter = 0; counter < getChildCount();
2533 counter++) {
2534 TableCellElementInfo cell = (TableCellElementInfo)
2535 getChild(counter);
2536
2537 if (cell.getRowCount() >= rowspan) {
2538 cols += cell.getColumnCount();
2539 }
2540 }
2541 return cols;
2542 }
2543 return 0;
2544 }
2545 }
2546
2547 /**
2548 * TableCellElementInfo is used to represents the cells of
2549 * the table.
2550 */
2551 private class TableCellElementInfo extends ElementInfo {
2552
2553 private Accessible accessible;
2554 private boolean isHeaderCell;
2555
2556 TableCellElementInfo(Element e, ElementInfo parent) {
2557 super(e, parent);
2558 this.isHeaderCell = false;
2559 }
2560
2561 TableCellElementInfo(Element e, ElementInfo parent,
2562 boolean isHeaderCell) {
2563 super(e, parent);
2564 this.isHeaderCell = isHeaderCell;
2565 }
2566
2567 /*
2568 * Returns whether this table cell is a header
2569 */
2570 public boolean isHeaderCell() {
2571 return this.isHeaderCell;
2572 }
2573
2574 /*
2575 * Returns the Accessible representing this table cell
2576 */
2577 public Accessible getAccessible() {
2578 accessible = null;
2579 getAccessible(this);
2580 return accessible;
2581 }
2582
2583 /*
2584 * Gets the outermost Accessible in the table cell
2585 */
2586 private void getAccessible(ElementInfo elementInfo) {
2587 if (elementInfo instanceof Accessible) {
2588 accessible = (Accessible)elementInfo;
2589 } else {
2590 for (int i = 0; i < elementInfo.getChildCount(); i++) {
2591 getAccessible(elementInfo.getChild(i));
2592 }
2593 }
2594 }
2595
2596 /**
2597 * Returns the rowspan attribute.
2598 */
2599 public int getRowCount() {
2600 if (validateIfNecessary()) {
2601 return Math.max(1, getIntAttr(getAttributes(),
2602 HTML.Attribute.ROWSPAN, 1));
2603 }
2604 return 0;
2605 }
2606
2607 /**
2608 * Returns the colspan attribute.
2609 */
2610 public int getColumnCount() {
2611 if (validateIfNecessary()) {
2612 return Math.max(1, getIntAttr(getAttributes(),
2613 HTML.Attribute.COLSPAN, 1));
2614 }
2615 return 0;
2616 }
2617
2618 /**
2619 * Overriden to invalidate the TableRowElementInfo as well as
2620 * the TableCellElementInfo.
2621 */
2622 protected void invalidate(boolean first) {
2623 super.invalidate(first);
2624 getParent().invalidate(true);
2625 }
2626 }
2627 }
2628
2629
2630 /**
2631 * ElementInfo provides a slim down view of an Element. Each ElementInfo
2632 * can have any number of child ElementInfos that are not necessarily
2633 * direct children of the Element. As the Document changes various
2634 * ElementInfos become invalidated. Before accessing a particular portion
2635 * of an ElementInfo you should make sure it is valid by invoking
2636 * <code>validateIfNecessary</code>, this will return true if
2637 * successful, on the other hand a false return value indicates the
2638 * ElementInfo is not valid and can never become valid again (usually
2639 * the result of the Element the ElementInfo encapsulates being removed).
2640 */
2641 private class ElementInfo {
2642
2643 /**
2644 * The children of this ElementInfo.
2645 */
2646 private ArrayList<ElementInfo> children;
2647 /**
2648 * The Element this ElementInfo is providing information for.
2649 */
2650 private Element element;
2651 /**
2652 * The parent ElementInfo, will be null for the root.
2653 */
2654 private ElementInfo parent;
2655 /**
2656 * Indicates the validity of the ElementInfo.
2657 */
2658 private boolean isValid;
2659 /**
2660 * Indicates if the ElementInfo can become valid.
2661 */
2662 private boolean canBeValid;
2663
2664
2665 /**
2666 * Creates the root ElementInfo.
2667 */
2668 ElementInfo(Element element) {
2669 this(element, null);
2670 }
2671
2672 /**
2673 * Creates an ElementInfo representing <code>element</code> with
2674 * the specified parent.
2675 */
2676 ElementInfo(Element element, ElementInfo parent) {
2677 this.element = element;
2678 this.parent = parent;
2679 isValid = false;
2680 canBeValid = true;
2681 }
2682
2683 /**
2684 * Validates the receiver. This recreates the children as well. This
2685 * will be invoked within a <code>readLock</code>. If this is overriden
2686 * it MUST invoke supers implementation first!
2687 */
2688 protected void validate() {
2689 isValid = true;
2690 loadChildren(getElement());
2691 }
2692
2693 /**
2694 * Recreates the direct children of <code>info</code>.
2695 */
2696 protected void loadChildren(Element parent) {
2697 if (!parent.isLeaf()) {
2698 for (int counter = 0, maxCounter = parent.getElementCount();
2699 counter < maxCounter; counter++) {
2700 Element e = parent.getElement(counter);
2701 ElementInfo childInfo = createElementInfo(e, this);
2702
2703 if (childInfo != null) {
2704 addChild(childInfo);
2705 }
2706 else {
2707 loadChildren(e);
2708 }
2709 }
2710 }
2711 }
2712
2713 /**
2714 * Returns the index of the child in the parent, or -1 for the
2715 * root or if the parent isn't valid.
2716 */
2717 public int getIndexInParent() {
2718 if (parent == null || !parent.isValid()) {
2719 return -1;
2720 }
2721 return parent.indexOf(this);
2722 }
2723
2724 /**
2725 * Returns the Element this <code>ElementInfo</code> represents.
2726 */
2727 public Element getElement() {
2728 return element;
2729 }
2730
2731 /**
2732 * Returns the parent of this Element, or null for the root.
2733 */
2734 public ElementInfo getParent() {
2735 return parent;
2736 }
2737
2738 /**
2739 * Returns the index of the specified child, or -1 if
2740 * <code>child</code> isn't a valid child.
2741 */
2742 public int indexOf(ElementInfo child) {
2743 ArrayList children = this.children;
2744
2745 if (children != null) {
2746 return children.indexOf(child);
2747 }
2748 return -1;
2749 }
2750
2751 /**
2752 * Returns the child ElementInfo at <code>index</code>, or null
2753 * if <code>index</code> isn't a valid index.
2754 */
2755 public ElementInfo getChild(int index) {
2756 if (validateIfNecessary()) {
2757 ArrayList<ElementInfo> children = this.children;
2758
2759 if (children != null && index >= 0 &&
2760 index < children.size()) {
2761 return children.get(index);
2762 }
2763 }
2764 return null;
2765 }
2766
2767 /**
2768 * Returns the number of children the ElementInfo contains.
2769 */
2770 public int getChildCount() {
2771 validateIfNecessary();
2772 return (children == null) ? 0 : children.size();
2773 }
2774
2775 /**
2776 * Adds a new child to this ElementInfo.
2777 */
2778 protected void addChild(ElementInfo child) {
2779 if (children == null) {
2780 children = new ArrayList<ElementInfo>();
2781 }
2782 children.add(child);
2783 }
2784
2785 /**
2786 * Returns the View corresponding to this ElementInfo, or null
2787 * if the ElementInfo can't be validated.
2788 */
2789 protected View getView() {
2790 if (!validateIfNecessary()) {
2791 return null;
2792 }
2793 Object lock = lock();
2794 try {
2795 View rootView = getRootView();
2796 Element e = getElement();
2797 int start = e.getStartOffset();
2798
2799 if (rootView != null) {
2800 return getView(rootView, e, start);
2801 }
2802 return null;
2803 } finally {
2804 unlock(lock);
2805 }
2806 }
2807
2808 /**
2809 * Returns the Bounds for this ElementInfo, or null
2810 * if the ElementInfo can't be validated.
2811 */
2812 public Rectangle getBounds() {
2813 if (!validateIfNecessary()) {
2814 return null;
2815 }
2816 Object lock = lock();
2817 try {
2818 Rectangle bounds = getRootEditorRect();
2819 View rootView = getRootView();
2820 Element e = getElement();
2821
2822 if (bounds != null && rootView != null) {
2823 try {
2824 return rootView.modelToView(e.getStartOffset(),
2825 Position.Bias.Forward,
2826 e.getEndOffset(),
2827 Position.Bias.Backward,
2828 bounds).getBounds();
2829 } catch (BadLocationException ble) { }
2830 }
2831 } finally {
2832 unlock(lock);
2833 }
2834 return null;
2835 }
2836
2837 /**
2838 * Returns true if this ElementInfo is valid.
2839 */
2840 protected boolean isValid() {
2841 return isValid;
2842 }
2843
2844 /**
2845 * Returns the AttributeSet associated with the Element, this will
2846 * return null if the ElementInfo can't be validated.
2847 */
2848 protected AttributeSet getAttributes() {
2849 if (validateIfNecessary()) {
2850 return getElement().getAttributes();
2851 }
2852 return null;
2853 }
2854
2855 /**
2856 * Returns the AttributeSet associated with the View that is
2857 * representing this Element, this will
2858 * return null if the ElementInfo can't be validated.
2859 */
2860 protected AttributeSet getViewAttributes() {
2861 if (validateIfNecessary()) {
2862 View view = getView();
2863
2864 if (view != null) {
2865 return view.getElement().getAttributes();
2866 }
2867 return getElement().getAttributes();
2868 }
2869 return null;
2870 }
2871
2872 /**
2873 * Convenience method for getting an integer attribute from the passed
2874 * in AttributeSet.
2875 */
2876 protected int getIntAttr(AttributeSet attrs, Object key, int deflt) {
2877 if (attrs != null && attrs.isDefined(key)) {
2878 int i;
2879 String val = (String)attrs.getAttribute(key);
2880 if (val == null) {
2881 i = deflt;
2882 }
2883 else {
2884 try {
2885 i = Math.max(0, Integer.parseInt(val));
2886 } catch (NumberFormatException x) {
2887 i = deflt;
2888 }
2889 }
2890 return i;
2891 }
2892 return deflt;
2893 }
2894
2895 /**
2896 * Validates the ElementInfo if necessary. Some ElementInfos may
2897 * never be valid again. You should check <code>isValid</code> before
2898 * using one. This will reload the children and invoke
2899 * <code>validate</code> if the ElementInfo is invalid and can become
2900 * valid again. This will return true if the receiver is valid.
2901 */
2902 protected boolean validateIfNecessary() {
2903 if (!isValid() && canBeValid) {
2904 children = null;
2905 Object lock = lock();
2906
2907 try {
2908 validate();
2909 } finally {
2910 unlock(lock);
2911 }
2912 }
2913 return isValid();
2914 }
2915
2916 /**
2917 * Invalidates the ElementInfo. Subclasses should override this
2918 * if they need to reset state once invalid.
2919 */
2920 protected void invalidate(boolean first) {
2921 if (!isValid()) {
2922 if (canBeValid && !first) {
2923 canBeValid = false;
2924 }
2925 return;
2926 }
2927 isValid = false;
2928 canBeValid = first;
2929 if (children != null) {
2930 for (ElementInfo child : children) {
2931 child.invalidate(false);
2932 }
2933 children = null;
2934 }
2935 }
2936
2937 private View getView(View parent, Element e, int start) {
2938 if (parent.getElement() == e) {
2939 return parent;
2940 }
2941 int index = parent.getViewIndex(start, Position.Bias.Forward);
2942
2943 if (index != -1 && index < parent.getViewCount()) {
2944 return getView(parent.getView(index), e, start);
2945 }
2946 return null;
2947 }
2948
2949 private int getClosestInfoIndex(int index) {
2950 for (int counter = 0; counter < getChildCount(); counter++) {
2951 ElementInfo info = getChild(counter);
2952
2953 if (index < info.getElement().getEndOffset() ||
2954 index == info.getElement().getStartOffset()) {
2955 return counter;
2956 }
2957 }
2958 return -1;
2959 }
2960
2961 private void update(DocumentEvent e) {
2962 if (!isValid()) {
2963 return;
2964 }
2965 ElementInfo parent = getParent();
2966 Element element = getElement();
2967
2968 do {
2969 DocumentEvent.ElementChange ec = e.getChange(element);
2970 if (ec != null) {
2971 if (element == getElement()) {
2972 // One of our children changed.
2973 invalidate(true);
2974 }
2975 else if (parent != null) {
2976 parent.invalidate(parent == getRootInfo());
2977 }
2978 return;
2979 }
2980 element = element.getParentElement();
2981 } while (parent != null && element != null &&
2982 element != parent.getElement());
2983
2984 if (getChildCount() > 0) {
2985 Element elem = getElement();
2986 int pos = e.getOffset();
2987 int index0 = getClosestInfoIndex(pos);
2988 if (index0 == -1 &&
2989 e.getType() == DocumentEvent.EventType.REMOVE &&
2990 pos >= elem.getEndOffset()) {
2991 // Event beyond our offsets. We may have represented this,
2992 // that is the remove may have removed one of our child
2993 // Elements that represented this, so, we should foward
2994 // to last element.
2995 index0 = getChildCount() - 1;
2996 }
2997 ElementInfo info = (index0 >= 0) ? getChild(index0) : null;
2998 if (info != null &&
2999 (info.getElement().getStartOffset() == pos) && (pos > 0)) {
3000 // If at a boundary, forward the event to the previous
3001 // ElementInfo too.
3002 index0 = Math.max(index0 - 1, 0);
3003 }
3004 int index1;
3005 if (e.getType() != DocumentEvent.EventType.REMOVE) {
3006 index1 = getClosestInfoIndex(pos + e.getLength());
3007 if (index1 < 0) {
3008 index1 = getChildCount() - 1;
3009 }
3010 }
3011 else {
3012 index1 = index0;
3013 // A remove may result in empty elements.
3014 while ((index1 + 1) < getChildCount() &&
3015 getChild(index1 + 1).getElement().getEndOffset() ==
3016 getChild(index1 + 1).getElement().getStartOffset()){
3017 index1++;
3018 }
3019 }
3020 index0 = Math.max(index0, 0);
3021 // The check for isValid is here as in the process of
3022 // forwarding update our child may invalidate us.
3023 for (int i = index0; i <= index1 && isValid(); i++) {
3024 getChild(i).update(e);
3025 }
3026 }
3027 }
3028 }
3029
3030 /**
3031 * DocumentListener installed on the current Document. Will invoke
3032 * <code>update</code> on the <code>RootInfo</code> in response to
3033 * any event.
3034 */
3035 private class DocumentHandler implements DocumentListener {
3036 public void insertUpdate(DocumentEvent e) {
3037 getRootInfo().update(e);
3038 }
3039 public void removeUpdate(DocumentEvent e) {
3040 getRootInfo().update(e);
3041 }
3042 public void changedUpdate(DocumentEvent e) {
3043 getRootInfo().update(e);
3044 }
3045 }
3046
3047 /*
3048 * PropertyChangeListener installed on the editor.
3049 */
3050 private class PropertyChangeHandler implements PropertyChangeListener {
3051 public void propertyChange(PropertyChangeEvent evt) {
3052 if (evt.getPropertyName().equals("document")) {
3053 // handle the document change
3054 setDocument(editor.getDocument());
3055 }
3056 }
3057 }
3058 }